<?php
//$Id: heartbeatactivity.inc,v 1.1.2.1.2.18 2009/11/15 14:41:53 stalski Exp $


/**
 * Class defines an activity message object
 */
class HeartbeatActivity {

  public $uaid = 0;
  public $message_id = '';
  public $uid = 0;
  public $uid_access = TRUE;
  public $uid_target = 0;
  public $nid = 0;
  public $nid_access = TRUE;
  public $nid_target = 0;
  public $nid_target_access = TRUE;
  public $timestamp = 0;
  public $message = '';
  public $message_concat = '';
  public $message_orig = '';
  public $message_concat_orig = '';
  public $concat_args = array();
  public $access = HEARTBEAT_PUBLIC_TO_ALL;

  // Inherited from heartbeat message
  public $hid = 0;
  public $description = '';
  public $roles = array();
  public $perms = 0;
  public $custom = 0;
  public $variables = array();
  public $attachments = array();
  public $tags = array();

  // Variables that hold logic
  // count for same instance
  public $count = 0;
  // count for same instance that is merged by target
  public $target_count = 0;
  public $display_time = TRUE;

  /**
   * constructor
   */
  function __construct($data = null) {
    if(isset($data)) {
      $this->set_data($data);
    }
  }

  /**
   * Set data into members
   */
  public function set_data($data) {

  	if(is_object($data)) {
  		$data = (array) $data;
  	}

    if (isset($data['uaid'])) {
    	$this->uaid = $data['uaid'];
    }
    if (isset($data['count'])) {
    	$this->count = $data['count'];
    }
    $this->message_id = $data['message_id'];
    $this->uid = $data['uid'];
    $this->uid_target = $data['uid_target'];
    $this->nid = $data['nid'];
    $this->nid_target = isset($data['nid_target']) ? (int)$data['nid_target'] : 0;
    $this->timestamp = isset($data['timestamp']) ? $data['timestamp'] : $_SERVER['REQUEST_TIME'];

    $this->message = $data['message'];
    if (isset($data['message_orig'])){
      $this->message_orig = $data['message_orig'];
    }

    $this->message_concat = $data['message_concat'];
    if (isset($data['message_concat_orig'])){
      $this->message_concat_orig = $data['message_concat_orig'];
      if (empty($data['message_concat'])){
        $this->message_concat = $data['message_concat_orig'];
      }
    }


    if (isset($data['concat_args'])) {
    	$this->concat_args = heartbeat_decode_message_variables($data['concat_args']);
    }

    $this->set_roles(isset($this->concat_args['roles']) ? $this->concat_args['roles'] : array());

    if (isset($data['hid'])){
    	$this->hid = $data['hid'];
    	if (variable_get('heartbeat_activity_display_tags', 0) == 1) {
        $this->tags = heartbeat_get_available_tags($this->hid);
    	}
    }

    if (isset($data['description'])){
      $this->description = $data['description'];
    }

    if (isset($data['perms'])) {
    	$this->perms = $data['perms'];
    }
    if (isset($data['custom'])){
    	$this->custom = $data['custom'];
    }
    if (isset($data['access']) && is_numeric($data['access'])) {
      $this->access = $data['access'];
    }

    if (isset($data['attachments'])){
    	$this->set_attachments($data['attachments']);
    }

    $this->set_variables($data['variables']);

    if (isset($data['extra']['duplicate'])) {
      $this->variables['duplicate'] = $data['extra']['duplicate'];
    }
  }

  /**
   * Sets the variables array
   * Data variables are stored in an array to use them to build real variables.
   * this means that when activity message objects get merged, the variables
   * will be filled with variables components from all instances.
   */
  private function set_variables($variables = NULL) {

    if(!empty($variables)) {

    	if (is_string($variables)) {
        $this->variables = heartbeat_decode_message_variables($variables);
      }
      elseif (is_array($variables)) {
      	$this->variables = $variables;
      }
    }
  }

  /**
   * Sets the attachments
   */
  private function set_attachments($attachments = array()) {

    if(!empty($attachments)) {
      if (is_string($attachments)) {
        $this->attachments = unserialize($attachments);
      }
      if (is_array($attachments)) {
        $this->attachments = $attachments;
      }
    }
  }

  /**
   * Sets the roles
   */
  private function set_roles($roles = array()) {

    // Filter the roles out of the arguments
    if (!empty($roles) && is_array($roles)) {
    	foreach ($roles as $rid) {
    		if ($rid != 0) {
          $this->roles[$rid] = $rid;
          //$this->roles[$rid] = db_result(db_query('SELECT r.name FROM {role} r WHERE r.rid=%d LIMIT 1', $rid));
    		}
    	}
    }
    // if no roles have been set, no role-based restrictions
    if ($this->roles == array()) {
    	$this->roles[DRUPAL_ANONYMOUS_RID] = DRUPAL_ANONYMOUS_RID;
    }
  }

  /**
   * Method gets a heartbeat_activity variable
   *
   * @desc The magic getter method fetches a variable
   *       in members. If not found there, it will always
   *       check the variables as well
   */
  public function __get($variable) {
    // a private member is asked
    $var = null;
    if(property_exists($this, $variable ) ) {
      $var = $this->{$variable};
    }
    else if(array_key_exists($variable, $this->variables)) {
      $var = $this->variables[$variable];
    }
    return $var;
  }

  /**
   * Method sets a heartbeat_activity property
   *
   * @desc The magic setter method sets a variable to member
   *  If not found, just stash in the variables array
   */
  public function __set($variable, $value) {
    if( property_exists($this, $variable) ) {
    	$this->{$variable} = $value;
    }
    else {
    	$this->variables[$variable] = $value;
    }
  }

  /**
   * Public function to save activity to database
   * @param array raw argument to enforce as is (pre-renderd)
   */
  public function save($raw_args = array()) {

    if (module_exists('locale')) {
      return $this->save_locale($raw_args);
    }
    else {
      return $this->_save($raw_args);
    }
  }

  /**
   * Save activity log with multilingual content
   * and multilingual parts to pre-translate
   *
   * @param array $raw_args
   */
  private function save_locale($raw_args = array()) {

    $logged = FALSE;
    $args = $this->rebuild_arguments($raw_args, true);
    $locale = $args['locale'];
    unset($args['locale']);
    // Save activity by logging a row for each active language
    // Translations only when locale exists
    $languages = locale_language_list();
    foreach ($languages as $language => $human_language) {
      // preprocess multilingual message "parts"
      // for all flagged token replacements
      foreach ($this->variables as $key => $value) {
        if(isset($locale[$key])) {
          $amp_token = str_replace("#","!",$key);
          $args[$amp_token] = locale($locale[$key], $language);
        }
      }
      $logged = $this->log_message($args,$language);
    }
    return $logged;
  }

  /**
   * Save activity log
   *
   * @param array $raw_args
   */
  private function _save($raw_args = array()) {
    // Rebuild arguments with tokens
    $args = $this->rebuild_arguments($raw_args);
    return $this->log_message($args);
  }

  /**
   * Logs a heartbeat message
   * @param string language optional
   *
   */
  private function log_message($args, $lang = '') {
    if($lang == '') {
      global $language;
      $lang = $language->language;
    }
    // Mis-usage of the t-function, but how could i fix this?
    $message = t($this->message, $args, $lang);
    $message_concat = t($this->message_concat, $args, $lang);
    // Checks if there should be logging what so ever
    if(empty($message)){
      watchdog('heartbeat', 'Error in logging user activity: it is not possible to log empty message', array(), WATCHDOG_ERROR);
      return false;
    }
    if(!is_numeric($this->uid) || $this->uid <= 0){
      watchdog('heartbeat', 'Error in logging user activity: no information on the user invoking heartbeat activity', array(), WATCHDOG_ERROR);
      return false;
    }

    // Log relational message to user activity
    return db_query("INSERT INTO {heartbeat_activity}
    SET uid=%d, uid_target=%d, nid=%d, nid_target=%d, message_id='%s', language='%s',
    access = %d, message ='%s',message_concat ='%s', timestamp=%d, variables='%s'",
    $this->uid,  $this->uid_target, $this->nid, $this->nid_target, $this->message_id, $lang,
    $this->access, $message, $message_concat, $_SERVER['REQUEST_TIME'], heartbeat_encode_message_variables($this->variables));

  }

  /**
   * Rebuild the arguments for variables
   * to share within this object
   *
   * @param array $raw_input of arguments
   */
  private function rebuild_arguments($raw_input, $locale = false) {
    $args = array();
    if ($locale) {
      // Variables that need to be pre-translated go here
      $args['locale'] = array();
    }

    $tags = heartbeat_allowed_tags();

    // Rebuild arguments with language tokens
    foreach ($this->variables as $key => $value) {

    	$value = filter_xss($value, $tags);

      // Leave $key[0] == "!"  asis
      if($key[0] != "@" && $key[0] != "#" ) {
        //continue; // bad argument
      }
      $oldkey = $key;
      // Reset the key of the arguments to ! to parse the next
      // tokenization asis.
      if($key[0] == "@") $key[0] = "!";
      // # and @ token replacement prefixes are kept,
      // but set a flag for it in the raw_arguments
      if($key[0] == "#") {
        // if it has to be translated ...
        if($locale) $args['locale'][$key] = $value;
        // Now reset the key
        $key[0] = "!";
      }
      // if argument is prefilled, override
      if (isset($raw_input[$oldkey])) {
        $args[$key] = $raw_input[$oldkey];
        continue;
      }
      // Argument gets the value as in variables
      $args[$key] = $value;
    }

    return $args;
  }

  /*
   * Rebuild the message with given candidate variables
   */
  public function rebuild_message() {

  	if (count($this->variables) > 0) {
  		$variables = array();
  	  foreach ($this->variables as $key => $value) {
  	  	$key[0] = '!';
  	    $variables[$key] = $value;
  	  }
  	}

  	return t($this->message_orig, $variables);
  }

  /*
   * Rebuild the message with given candidate variables
   */
  public function create_grouped_message($candidates, $max_items_to_group = 5) {

    global $base_url;

    $message_template = $this->message_concat;
    $message_extension = '';
    $message_template = str_replace("%times%", $this->target_count, $message_template);
    $message_template = str_replace("%count%", $this->target_count, $message_template);

    // Prepare the merged factors from the stored messages
    $merged_string = '';

    $remains = array();
    $target = $this->concat_args['group_target'];
    $beat = 'beat-item-' . $this->uaid;
    $unique = $candidates['variables'];
    $count = $candidates['count'];//count($candidates['variables']);

    // Limit the number of displayed (grouped) messages by default or by message
    if (isset($this->concat_args['group_num_max'])) {
    	$max_items_to_group = $this->concat_args['group_num_max'];
    }

    if ($count > $max_items_to_group) {
      $count = $max_items_to_group;
      $unique = array_slice($unique, 0, $count);
      // Add the remains by keeping the others plus the last one,
      // in case the total exceeds the max group setting.
      $remains = array_slice($candidates['variables'], $count - 1);
    }

    // Replacement of placeholder with group targets
    // If there is a match for %variable%
    // Try to replace the variable with the group_target variables
    if (preg_match_all("|\%(.*)\%(.*)|U", $message_template, $matches)) {

      if (count($remains) > 0) {
      	$hidden_remains = '<ul id="' . $beat . '-remaining" style="display: none;">';
      	foreach($remains as $remain) {
      		$hidden_remains .= '<li>' . $remain["@".$target] . '</li>';
      	}
        $hidden_remains .= '</ul>';
      }

    	// TODO Make a decision on how to handle the target on merged groups
      // This code in comment is/was a attempt to have more grouping words
      // The difficult and almost impossible part is the fact that you always
      // have to target something to group on (for the same node or same user)
      //foreach($matches[1] as $target) {
        $placeholder = $matches[1][0];

        $i = 1;
        foreach($unique as $stored_variables) {

          // limit them to the value given by the group variable setting
          if (isset($stored_variables["@".$target])) {
            if ($i == 1) {
              $merged_string .= ' ' . $stored_variables["@".$target];
            }
            else if ($i < $count && $count > 2) {
              $merged_string .= ' ' . $this->concat_args['merge_separator'];
              $merged_string .= ' ' . $stored_variables["@".$target];
            }
            else if ($i == $count || $count == 2) {
              $merged_string .= ' ' . $this->concat_args['merge_end_separator'];
              if (count($remains) >= 1 && $hidden_remains != '') {
              	$attributes = array(
									'attributes' => array(
										'absolute' => TRUE,
										'onclick' => 'javascript:$(\'#' . $beat . '-remaining\').toggle(\'slow\'); return false;'),
										'fragment' => $beat);
              	$merged_string .= ' ' . l(t('@num more', array('@num' => count($remains))), $base_url . request_uri(), $attributes);
              	$message_extension .= $hidden_remains;
              }
              else {
                $merged_string .= ' ' . $stored_variables["@".$target];
              }
            }
          }
          $i++;
        }
        $message_template = str_replace("%".$placeholder."%", $merged_string, $message_template);
        if (isset($message_extension)) {
          $message_template .= $message_extension;
        }
      //}
    }
    $this->message = $message_template;
  }
} // eof class heartbeat_activity
